#include "TDC_V775N.h"
#include "vme_sis.h"
#include "sis3100_vme_calls.h"

// --- constructor ---
TDC_V775N::TDC_V775N(int handle,u_int32_t addr)
		: CAEN_module(handle,addr)
{
}


u_int32_t TDC_V775N::GetNChannels()
{
	return N_CHANNELS;
}


u_int32_t TDC_V775N::ReadEvent(EventStruct *ev)
{
		u_int32_t d;

		//clear event
		u_int16_t *adc_ptr = &(ev->adc00);

		for(int i=0; i<12; i++)	adc_ptr[i]=0xffff;
				
		//read header
		readA32D32(handle,address+OUTPUT_BUFFER,&d);

		//check if we got a header
		if( (d & IS_HEADER) != IS_HEADER )
				throw IOError(("TDC_V775N::readEvent(): header expected, got 0x"+
											 hex(d)).c_str(),0);

		// mask out number of memorised channels (= number of channels or number of buffer entries??)
		u_int16_t n_ch = (d & MEM_CHANNEL_MASK) >> MEM_CHANNEL_SHIFT;
		u_int32_t data[N_CHANNELS];
/*
		cerr << "TDC_V775N::ReadEvent(), got header 0x" << hex << d
				 << " -> reading data for " << dec << n_ch << " channels" << endl;
*/
		readA32D32DMA(handle,address+OUTPUT_BUFFER,data,n_ch);

		for(int i=0; i<n_ch; i++)	{
				if( (data[i] & IS_INVALID_DATUM) != 0 )
						throw IOError(("TDC_V775N::readEvent(): invalid datum, got "
													 +hex(data[i])).c_str(),0);

				u_int32_t ch    = (data[i] & CHANNEL_MASK) >> CHANNEL_SHIFT;
				u_int16_t value = (data[i] & VALUE_MASK) >> VALUE_SHIFT;
				
				if ((ch < 0) || (ch >11)) 
						throw IOError((string("TDC_V775N::readEvent(): got data for channel ")+ch+"("+hex(data[i])+")").c_str(),0);
				
				adc_ptr[ch] = value;
		}
		
		//read eob
		readA32D32(handle,address+OUTPUT_BUFFER,&d);

		//check if we got eob
		if( (d & IS_EOB) != IS_EOB ) 
				throw IOError(("TDC_V775N::readEvent(): eob expected, got "+hex(d)).c_str(),0);

		//return the event counter, extracted from eob

		return (d & EOB_EVCOUNT_MASK);
}

u_int32_t TDC_V775N::ReadEventFast(EventStruct *ev)
{
    u_int32_t data[PERKEO_CN+2];
				
		//read 14 words at once
		readA32D32DMA(handle, address+OUTPUT_BUFFER, data, PERKEO_CN+2);

		//check if we got a header
		if( (data[0] & IS_HEADER) != IS_HEADER )
				throw IOError(("TDC_V775N::readEvent(): header expected, got 0x"+
											 hex(data[0])).c_str(),0);

		u_int32_t n_ch = (data[0] & MEM_CHANNEL_MASK) >> MEM_CHANNEL_SHIFT;
		if( n_ch != PERKEO_CN )
				throw IOError( ( string("TDC_V775N::readEvent(): number of events extracted from header is ")
												 + n_ch + " - expected " + PERKEO_CN ).c_str(),0);
		
		//check if we got eob
		if( (data[PERKEO_CN+1] & IS_EOB) != IS_EOB ) 
				throw	IOError(("TDC_V775N::readEvent(): eob expected, got "
											 +hex(data[PERKEO_CN+1])).c_str(),0);

		//clear event
		u_int16_t *adc_ptr = &(ev->adc00);
		for(int i=0; i<12; i++)	adc_ptr[i]=0xffff;

		for(int i=1; i<PERKEO_CN+1; i++) {
				if( (data[i] & IS_INVALID_DATUM) != 0 )
						throw IOError(("TDC_V775N::readEvent(): invalid datum, got "
													 +hex(data[i])).c_str(),0);

				u_int32_t ch    = (data[i] & CHANNEL_MASK) >> CHANNEL_SHIFT;
				u_int16_t value = (data[i] & VALUE_MASK) >> VALUE_SHIFT;

				if( (ch < 0) || (ch >11) ) 
						throw IOError((string("TDC_V775N::readEvent(): got data for channel ")
													 +ch+"("+hex(data[i])+")").c_str(),0);
				adc_ptr[ch] = value;
		}
		
		//return the event counter, extracted from eob
		return (data[PERKEO_CN+1] & EOB_EVCOUNT_MASK);
}


u_int32_t TDC_V775N::ReadEvents(EventStruct *ev,u_int32_t num)
{
		const static u_int32_t MAXEVENTS = 32;
		static u_int32_t data[MAXEVENTS*(PERKEO_CN+2)];

		if (num > MAXEVENTS)
			throw IOError( ( string("TDC_V775N::readEvents(): "
									 "number of events is ")
									 + num + " - maximum is " + MAXEVENTS ).c_str(),0);

		// number of words to read from QDCs
		//   = number of events * number of TDC channels + 2 (for start and stop words))
		u_int32_t words = num*(PERKEO_CN+2);
				
		// read all data at once
		readA32D32DMA(handle, address + OUTPUT_BUFFER, data, words);
		
		// test different access modes:
		//u_int32_t read;
		//vme_A32DMA_D32_read(handle, address + OUTPUT_BUFFER, data, words, &read);
		//vme_A32BLT32_read(handle, address + OUTPUT_BUFFER, data, words, &read);
		//vme_A32DMA_D32FIFO_read(handle, address + OUTPUT_BUFFER, data, words, &read);
		
		u_int32_t offset;
		u_int32_t j;
		offset = 0;
		for (j=0; j<num; j++) {
				// offset = j * size of data set
				offset = j*(PERKEO_CN+2);
								
				// check for invalid datum, i.e. empty buffer
				if ((data[offset+0] & IS_INVALID_DATUM) == IS_INVALID_DATUM)
					throw IOError( ( 
							string("TDC_V775N::ReadEvents(#") + j + "):" +
							"got header marked as invalid: 0x" +
							hex(data[offset+0]) 
								   ).c_str(), 0);				
				
				//check if we got a header
				if ((data[offset+0] & HEADER_MASK) != IS_HEADER )
					throw IOError( ( 
							string("TDC_V775N::ReadEvents(#") + j + "):" +
							"header expected, got 0x" +
							hex(data[offset+0]) 
								   ).c_str(), 0);
				
				//check if the number of channels is correct
				u_int32_t n_ch = (data[offset+0] & MEM_CHANNEL_MASK) >> MEM_CHANNEL_SHIFT;
				if (n_ch != PERKEO_CN)
						throw IOError( ( 
							string("TDC_V775N::ReadEvents(#") + j + "):" +
							"number of events extracted from header is "
	   		  		 		+ n_ch + " - expected " + PERKEO_CN +
							" - header is 0x" + hex(data[offset+0]) ).c_str(), 0);
				
				// check if we got eob
				if ((data[offset+PERKEO_CN+1] & IS_EOB) != IS_EOB) 
						throw	IOError( ( 
							string("TDC_V775N::ReadEvents(#") + j + "):" +
							"eob expected, got "
							+ hex(data[offset+PERKEO_CN+1])).c_str(), 0);
				
				// clear event
				u_int16_t *adc_ptr = &(ev[j].adc00);
				for (int i=0; i<12; i++)	adc_ptr[i]=0xffff;
				
				for (u_int32_t i=1; i<n_ch+1; i++)	{
						if( (data[offset+i] & IS_INVALID_DATUM) != 0 )
								throw IOError(("TDC_V775N::readEvents(): invalid datum, got "
															 +hex(data[offset+i])).c_str(),0);

						u_int32_t ch    = (data[offset+i] & CHANNEL_MASK) >> CHANNEL_SHIFT;
						u_int16_t value = (data[offset+i] & VALUE_MASK) >> VALUE_SHIFT;

						if( (ch < 0) || (ch >11) ) 
						throw IOError((string("TDC_V775N::readEvents(): "
																	"got data for channel ")
													 +ch+"("+hex(data[offset+i])+")").c_str(),0);
						
						// save only low range information
						adc_ptr[ch] = value;
				}
		}
		
		//return the event counter of the last event, extracted from its eob
    return (data[offset+PERKEO_CN+1] & EOB_EVCOUNT_MASK);
}

											  